﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using dotFitness.Domain.Tests.Stubs;

namespace dotFitness.Domain.Tests
{
    [TestClass]
    public class WorkoutProgramTests
    {
        [TestMethod]
        public void can_create_workout_program()
        {
            var program = new WorkoutProgram("Tri-Phase");

            Assert.AreEqual(program.Name, "Tri-Phase");
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void throws_when_constructor_called_with_null_name()
        {
            new WorkoutProgram(null);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void throws_when_constructor_called_with_empty_string_name()
        {
            new WorkoutProgram(string.Empty);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void throws_when_constructor_called_with_name_containing_white_space()
        {
            new WorkoutProgram(" ");
        }

        [TestMethod]
        public void can_change_workout_program_name()
        {
            var program = new WorkoutProgram("Tri-Phase");

            program.ChangeName("Tri Phase");

            Assert.AreEqual(program.Name, "Tri Phase");
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void throws_when_change_name_called_with_null()
        {
            var program = new WorkoutProgram("Tri-Phase");

            program.ChangeName(null);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void throws_when_change_name_called_with_empty_string()
        {
            var program = new WorkoutProgram("Tri-Phase");

            program.ChangeName(string.Empty);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void throws_when_change_name_called_with_white_space()
        {
            var program = new WorkoutProgram("Tri-Phase");

            program.ChangeName(" ");
        }

        [TestMethod]
        public void can_add_training_day_to_schedule()
        {
            var program = new WorkoutProgram("Tri-Phase");

            var day = program.Schedule.AddTrainingDay("Legs");

            Assert.IsNotNull(day);
            Assert.IsTrue(program.Schedule.Days.Contains(day));
            Assert.AreEqual(day.Day, 1);
            Assert.AreEqual(day.Name, "Legs");
            Assert.IsInstanceOfType(day, typeof(TrainingDay));
        }

        [TestMethod]
        public void can_change_day_name()
        {
            var program = new WorkoutProgram("Tri-Phase");

            var day = program.Schedule.AddTrainingDay("Legs");
            day.Name = "HIIT";

            Assert.AreEqual(day.Name, "HIIT");
        }

        [TestMethod]
        public void can_change_day_description()
        {
            var program = new WorkoutProgram("Tri-Phase");

            var day = program.Schedule.AddTrainingDay("Legs");
            day.Description = "Today is your leg day!";

            Assert.AreEqual(day.Description, "Today is your leg day!");
        }

        [TestMethod]
        public void calling_to_string_on_training_day_with_name_returns_day_with_name()
        {
            var program = new WorkoutProgram("Tri-Phase");

            var day = program.Schedule.AddTrainingDay("Legs");

            Assert.AreEqual(day.ToString(), "Day 1: Legs");
        }

        [TestMethod]
        public void calling_to_string_on_training_day_with_without_name_returns_day_only()
        {
            var program = new WorkoutProgram("Tri-Phase");

            var day = program.Schedule.AddTrainingDay();

            Assert.AreEqual(day.ToString(), "Day 1");
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void throws_when_passing_null_to_remove_day()
        {
            var program = new WorkoutProgram("Tri-Phase");

            program.Schedule.RemoveDay(null);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void throws_when_passing_day_from_another_schedule()
        {
            var program1 = new WorkoutProgram("Tri-Phase");
            var program2 = new WorkoutProgram("HIIT");

            var day = program2.Schedule.AddTrainingDay();

            program1.Schedule.RemoveDay(day);
        }

        [TestMethod]
        public void can_remove_day_from_schedule()
        {
            var program = new WorkoutProgram("Tri-Phase");

            var day = program.Schedule.AddTrainingDay();
            program.Schedule.RemoveDay(day);

            Assert.IsFalse(program.Schedule.Days.Contains(day));
        }

        [TestMethod]
        public void can_add_rest_day_to_schedule()
        {
            var program = new WorkoutProgram("Tri-Phase");

            var day = program.Schedule.AddRestDay();

            Assert.IsTrue(program.Schedule.Days.Contains(day));
            Assert.IsInstanceOfType(day, typeof(RestDay));
        }

        [TestMethod]
        public void calling_to_string_on_rest_day_with_name_returns_day_with_name()
        {
            var program = new WorkoutProgram("Tri-Phase");

            var day = program.Schedule.AddRestDay("Rest Day");

            Assert.AreEqual(day.ToString(), "Day 1: Rest Day");
        }

        [TestMethod]
        public void calling_to_string_on_rest_day_with_without_name_returns_day_only()
        {
            var program = new WorkoutProgram("Tri-Phase");

            var day = program.Schedule.AddRestDay();

            Assert.AreEqual(day.ToString(), "Day 1");
        }

        [TestMethod]
        public void can_add_cardio_day_to_schedule()
        {
            var program = new WorkoutProgram("Tri-Phase");

            var day = program.Schedule.AddCardioDay("HIIT");

            Assert.IsTrue(program.Schedule.Days.Contains(day));
            Assert.AreEqual(day.Name, "HIIT");
            Assert.IsInstanceOfType(day, typeof(CardioDay));
        }

        [TestMethod]
        public void calling_to_string_on_cardio_day_with_name_returns_day_with_name()
        {
            var program = new WorkoutProgram("Tri-Phase");

            var day = program.Schedule.AddCardioDay("HIIT");

            Assert.AreEqual(day.ToString(), "Day 1: HIIT");
        }

        [TestMethod]
        public void calling_to_string_on_cardio_day_with_without_name_returns_day_only()
        {
            var program = new WorkoutProgram("Tri-Phase");

            var day = program.Schedule.AddCardioDay();

            Assert.AreEqual(day.ToString(), "Day 1");
        }

        [TestMethod]
        public void can_swap_schedule_days()
        {
            var program = new WorkoutProgram("Tri-Phase");

            var day1 = program.Schedule.AddTrainingDay();
            var day2 = program.Schedule.AddTrainingDay();

            var day1Before = day1.Day;
            var day2Before = day2.Day;

            program.Schedule.SwapDays(day1, day2);

            Assert.AreEqual(day1.Day, day2Before);
            Assert.AreEqual(day2.Day, day1Before);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void throws_when_calling_swap_days_passing_left_null_day()
        {
            var program = new WorkoutProgram("Tri-Phase");

            var day1 = program.Schedule.AddTrainingDay();

            program.Schedule.SwapDays(null, day1);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void throws_when_calling_swap_days_passing_right_null_day()
        {
            var program = new WorkoutProgram("Tri-Phase");

            var day1 = program.Schedule.AddTrainingDay();

            program.Schedule.SwapDays(day1, null);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void throws_when_calling_swap_days_when_left_day_belongs_to_another_schedule()
        {
            var program1 = new WorkoutProgram("Tri-Phase");
            var program2 = new WorkoutProgram("HIIT");

            var day1 = program1.Schedule.AddTrainingDay();
            var day2 = program2.Schedule.AddTrainingDay();

            program1.Schedule.SwapDays(day2, day1);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void throws_when_calling_swap_days_when_right_day_belongs_to_another_schedule()
        {
            var program1 = new WorkoutProgram("Tri-Phase");
            var program2 = new WorkoutProgram("HIIT");

            var day1 = program1.Schedule.AddTrainingDay();
            var day2 = program2.Schedule.AddTrainingDay();

            program1.Schedule.SwapDays(day1, day2);
        }

        [TestMethod]
        public void can_add_exercise_to_schedule_day()
        {
            var program = new WorkoutProgram("Tri-Phase");

            var day = program.Schedule.AddTrainingDay();
            var exc = day.AddExercise(1, 2, 8, 12);

            Assert.IsNotNull(exc);
            Assert.AreEqual(day.Count(), 1);
            Assert.AreEqual(exc.ExerciseId, 1);
            Assert.AreEqual(exc.NumberOfSets, 2);
            Assert.AreEqual(exc.RepRange.MinReps, 8);
            Assert.AreEqual(exc.RepRange.MaxReps, 12);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void throws_when_calling_add_exercise_passing_less_than_one_number_of_sets()
        {
            var program = new WorkoutProgram("Tri-Phase");

            var day = program.Schedule.AddTrainingDay();
            var exc = day.AddExercise(1, 0, 8, 12);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void throws_when_calling_add_exercise_passing_less_than_one_min_reps()
        {
            var program = new WorkoutProgram("Tri-Phase");

            var day = program.Schedule.AddTrainingDay();
            var exc = day.AddExercise(1, 3, 0, 12);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void throws_when_calling_add_exercise_passing_less_than_one_max_reps()
        {
            var program = new WorkoutProgram("Tri-Phase");

            var day = program.Schedule.AddTrainingDay();
            var exc = day.AddExercise(1, 3, 8, 0);
        }

        [TestMethod]
        public void can_remove_exercise_from_schedule()
        {
            var program = new WorkoutProgram("Tri-Phase");

            var day = program.Schedule.AddTrainingDay();
            var scheduledExercise = day.AddExercise(1, 2, 8, 12);
            day.RemoveExercise(scheduledExercise);

            Assert.IsFalse(day.Contains(scheduledExercise));
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void throws_when_calling_remove_exercise_passing_null()
        {
            var program = new WorkoutProgram("Tri-Phase");

            var day = program.Schedule.AddTrainingDay();
            day.RemoveExercise(null);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void throws_when_calling_remove_exercise_passing_exercise_from_another_day()
        {
            var program1 = new WorkoutProgram("Tri-Phase");
            var program2 = new WorkoutProgram("HIIT");

            var day1 = program1.Schedule.AddTrainingDay();
            var day2 = program2.Schedule.AddTrainingDay();

            var scheduledExercise = day1.AddExercise(1, 2, 8, 12);

            day2.RemoveExercise(scheduledExercise);
        }
    }
}
